package com.iot.common.utils;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * java语言数字工具类，支持把其他语言的无符号的数字转换为java语言的数字类型，并且不会改变正负号。
 * 
 * java语言的基础数据类型都是有符号，当从其他语言接收到一个无符号的数字时，java语言会使用具有相同字节数的数字类型来接收，并可能改变其符号：
 * 比如： c语言的unsigned int32 值 0xffffffff， java语言的int类型接收后会识别为负数，而这并不是业务上所期望的，因此对于这类数据
 * 需要使用字节数多的数据类型来接收，比如上面的例子需要使用long数据类型来接收，如果是 c语言的unsigned int64值需要使用java的BigInteger来接收。
 *
 */
public abstract class UnsignedNumberUtils {
	/**
	 * 判断一个byte类型的数字是否为负数，如果为负数需要使用更宽类型的数据类型来接收，比如short，int，long等，一般使用short接收。
	 *
	 * @param value 
	 * @return
	 */
	public static boolean needWiden(byte value){
		// 0x80 = 10000000,即2的7次方，对于byte型数字来说就是符号位，如果符号位为1，则该数字为负数
		// 0x80也就是Byte.MIN_VALUE
		return (value & 0x80) == 0x80;
	}
	/**
	 * 判断一个int类型的数字是否为负数，如果为负数需要使用更宽类型的数据类型来接收，使用long接收。
	 *
	 * @param value 
	 * @return
	 */
	public static boolean needWiden(int value){
		// 0x80000000，即2的31次方，对于int型数字来说就是符号位，如果符号位为1，则该数字为负数
		// 0x80000000也就是Integer.MIN_VALUE
		return (value & 0x80000000) == 0x80000000;
	}
	/**
	 * 判断一个long类型的数字是否为负数，如果为负数需要BigInteger，这是因为java基础类型中字节数最多的就是long了，超过long表示范围的需要使用java库提供的
	 * BigInteger 对象类型
	 *
	 * @param value 
	 * @return
	 */
	public static boolean needWiden(long value){
		// 0x8000000000000000L，即2的63次方，对于int型数字来说就是符号位，如果符号位为1，则该数字为负数
		// 0x8000000000000000L也就是Long.MIN_VALUE
		// 注意后面那个L，如果没有这个L，java会认为这个数字为int类型
		return (value & 0x8000000000000000L) == 0x8000000000000000L;
	}
	/**
	 * 判断一个short类型的数字是否为负数，如果为负数需要使用更宽类型的数据类型来接收，比如int，long等，一般使用int接收。
	 *
	 * @param value 
	 * @return
	 */
	public static boolean needWiden(short value){
		// 0x80000，即2的15次方，对于int型数字来说就是符号位，如果符号位为1，则该数字为负数
		// 0x8000也就是Short.MIN_VALUE
		return (value & 0x8000) == 0x8000;
	}
	/**
	 * byte类型转换为short类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
	 * @param value
	 * @return
	 */
	public static short toUnsignedByteValue(byte value){
		if(!needWiden(value)){
			return value;
		}
		short wideValue = value;
		// 使用 与 运算把高为字节设置为0，符号位为0，该数字为正数
		return (short)(wideValue & 0xff);
	}
	/**
	 * int类型转换为long类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
	 * @param value
	 * @return
	 */
	public static long toUnsignedIntValue(int value){
		if(!needWiden(value)){
			return value;
		}
		long wideValue = value;
		// 使用 与 运算把高为字节设置为0，符号位为0，该数字为正数
		// 注意后面那个L，如果没有这个L，java会认为这个数字为int类型,在运算过程中他会自动把他转换为long类型，那么最终的返回的值就还是负数，这是由于字节少的数字类型转换为字节多的类型时太会
		// 自动用符号位填充高位字节，，举个例子,
		// 例子1
		// int value = 100;
		// long lv = value; 此时低4个字节填充的是100，而高4个字节是0
		// 例子3
		// int value = 0xf0000000;
		// long lv = value; 此时低4个字节是0xf0000000， 而高4个字节是1，因为0xf0000000的符号位是1
		// 至于为什么是这样，这里不再解释，考虑一下计算机内部负数是用补码表示的这一原则
		return wideValue & 0xffffffffL;
	}
	/**
	 * long类型转换为BigInteger类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
	 * @param value
	 * @return
	 */
	public static BigInteger toUnsignedLongValue(long value){
		if(!needWiden(value)){
			return BigInteger.valueOf(value);
		}
		// long型为8个字节，已经是占用字节最多的类型了，需要使用BigInteger来表示unsigned long了
		// 原理是获取long类型的字节数组，并把该数组作为BigInteger构造参数，然后构建BigInteger，
		// BigInteger所接收的byte数字是用大尾端表示的，因此在获取long的字节数组时使用ByteBuffer这个工具，
		// 关于大尾端、小尾端，请参见ByteOrder，或者一些操作系统相关的书籍
		ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/Byte.SIZE);
		buffer.order(ByteOrder.BIG_ENDIAN);
		buffer.putLong(value);
		return new BigInteger(1,buffer.array());
	}
	/**
	 * short类型转换为int类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
	 * @param value
	 * @return
	 */
	public static int toUnsignedShortValue(short value){
		if(!needWiden(value)){
			return value;
		}
		// 使用 与 运算把高为字节设置为0，符号位为0，该数字为正数
		int wideValue = value;
		return wideValue & 0xffff;
	}
}